package dryven.config.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import dryven.config.ApplicationConfiguration;
import dryven.config.xml.jdbctags.JdbcConfigurationElement;
import dryven.config.xml.jdbctags.MySqlConfigurationElement;
import dryven.util.ArrayIterable;

public class XMLConfigFileParser {
	
	private SAXParser _parser;
	private InputStream _document;
	private MemoryApplicationConfiguration _cfg;
	private Map<String,AbstractJdbcConfigurationElement> _jdbcElements = new HashMap<String, AbstractJdbcConfigurationElement>();
	
	public XMLConfigFileParser(InputStream document) {
		_document = document;
		try {
			_parser = SAXParserFactory.newInstance().newSAXParser();
		} catch (Exception e) {
			throw new RuntimeException("XML parser not available at this moment", e);
		}
		//add connection tags
		AddJdbcConfigurationElement(new MySqlConfigurationElement());
		AddJdbcConfigurationElement(new JdbcConfigurationElement());
	}
	
	private void AddJdbcConfigurationElement(AbstractJdbcConfigurationElement el) {
		_jdbcElements.put(el.getElementName(), el);
	}
	
	public ApplicationConfiguration parse() {
		_cfg = new MemoryApplicationConfiguration();
		try {
			_parser.parse(_document, new SaxHandler());
		} catch (SAXException e) {
			throw new RuntimeException("Error while parsing configuration file", e);
		} catch (IOException e) {
			throw new RuntimeException("Error while reading configuration file", e);
		}
		return _cfg;
	}
	
	class SaxHandler extends DefaultHandler {

		private String currentElement;
		private List<String> modelPackages = new LinkedList<String>();
		private List<String> controllerPackages = new LinkedList<String>();
		private List<String> viewPackages = new LinkedList<String>();
		
		private boolean inConnections = false;
		private boolean inMail = false;
		private boolean inCustom = false;
		private String defaultConnectionName = null;
		@Override
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			super.characters(ch, start, length);
		}

		@Override
		public void endElement(String uri, String localName, String qName)
				throws SAXException {
			super.endElement(uri, localName, qName);
			currentElement = qName;
			if(currentElement.equals("connections")) {
				inConnections = false;
				defaultConnectionName = null;
			} else if(currentElement.equals("mail")) {
				inMail = false;
			} else if(currentElement.equals("custom")) {
				inCustom = false;
			}
		}

		@Override
		public void startElement(String uri, String localName, String qName,
				Attributes attributes) throws SAXException {
			currentElement = qName;
			if(currentElement.equals("factory")) {
				_cfg.setFrontControllerFactoryClass(attributes.getValue("", "class"));
			} else if(currentElement.equals("views")) {
				String packageName = attributes.getValue("", "package");
				if(packageName!=null) {
					viewPackages.add(packageName);
				}
			} else if(currentElement.equals("controllers")) {
				String packageName = attributes.getValue("", "package");
				if(packageName!=null) {
					controllerPackages.add(packageName);
				}
			} else if(currentElement.equals("models")) {
				String packageName = attributes.getValue("", "package");
				if(packageName!=null) {
					modelPackages.add(packageName);
				}
			} else if(currentElement.equals("storage")) {
				_cfg.setUserDataDirectory(attributes.getValue("", "directory"));
			} else if(currentElement.equals("connections")) {
				inConnections = true;
				defaultConnectionName = attributes.getValue("", "default");
				return;
			} else if(currentElement.equals("mail")) {
				inMail = true;
				return;
			} else if(currentElement.equals("custom")) {
				inCustom = true;
				return;
			}
			if(inConnections) {
				AbstractJdbcConfigurationElement element = _jdbcElements.get(currentElement);
				if(element==null) {
					throw new RuntimeException(currentElement+" is not a valid connection element");
				}
				MemoryDatabaseConfiguration dbCfg = element.readConnection(attributes);
				String connectionName = attributes.getValue("", "name");
				dbCfg.setConnectionName(connectionName);
				_cfg.addDatabaseConfiguration(dbCfg, connectionName!=null && connectionName.equals(defaultConnectionName));
			}
			if(inMail) {
				MemoryMailSenderConfiguration mailCfg = (MemoryMailSenderConfiguration)_cfg.getMailSenderConfiguration();
				if(mailCfg==null) {
					mailCfg = new MemoryMailSenderConfiguration();
					_cfg.setMailSenderConfiguration(mailCfg);
				}
				if(currentElement.equals("smtp")) {
					String host = attributes.getValue("","host");
					String user = attributes.getValue("","user");
					String pass = attributes.getValue("","pass");
					String port = attributes.getValue("","port");
					String ssl = attributes.getValue("","ssl");
					mailCfg.setSMTP(host, user, pass, ssl.equalsIgnoreCase("true"), Integer.parseInt(port));
				}
				else if(currentElement.equals("override-recipient")) {
					String email = attributes.getValue("","email");
					mailCfg.setOverrideRecipient(email);
				}
				else if(currentElement.equals("subject-prefix")) {
					String text = attributes.getValue("","text");
					mailCfg.setSubjectPrefix(text);
				}
			}
		}

		@Override
		public void endDocument() throws SAXException {
			//set model, view and controller packages gathered
			if(controllerPackages.size()==0) {
				throw new RuntimeException("You have to set at least one controller package in the configuration");
			}
			String[] controllerPackagesArray = controllerPackages.toArray(new String[controllerPackages.size()]);
			_cfg.setControllerPackages(new ArrayIterable<String>(controllerPackagesArray));
			
			
			String[] modelPackagesArray = modelPackages.toArray(new String[modelPackages.size()]);
			_cfg.setModelPackages(new ArrayIterable<String>(modelPackagesArray));
			
			if(viewPackages.size()==0) {
				throw new RuntimeException("You have to set at least one view package in the configuration");
			}
			String[] viewPackagesArray = viewPackages.toArray(new String[viewPackages.size()]);
			_cfg.setViewPackages(new ArrayIterable<String>(viewPackagesArray));
		}
		
	}
}
